<template>
  <div :id="container" v-loading="loading" class="sub-app"></div>
</template>
<script>
/*
 * File Created: 2022-07-13 10:20:01
 * Author: yubaolin (yubaolin6@outlook.com)
 * Desc: 子应用组件，用于手动挂载子应用
 * Last Modified: 2022-07-13 10:22:27
 * Modified By: yubaolin (yubaolin6@outlook.com>)
 */
import {onMounted, reactive, toRefs, onBeforeUnmount, getCurrentInstance, watch, onDeactivated, onActivated} from 'vue'
import {loadMicroApp} from 'qiankun'

// 缓存子应用文件加载状态，同一个子应用文件只加载一次
const cacheLoadStatus = new Map()

// 组件id索引
let idIndex = 1;

/**
 * configuration 具体配置看qiankun官方文档
 */
export default {
  name: 'LoadMicroApp',
  props: {
    appName: { // 微应用的名称
      require: true,
      type: String,
      default: ''
    },
    entry: {
      require: true,
      type: [String, Array],
      default: ''
    },
    componentName: { // 必须传入，子应用用该字段值匹配组件渲染
      require: true,
      type: String,
      default: ''
    },
    stateData: {
      type: Object,
      default() {
        return {}
      }
    },
    extends: { // 需要appGlobal上的数据
      type: Object,
      default() {
        return {}
      }
    },
    configuration:{
      type: Object,
      default() {
        return {
          sandbox: {
            experimentalStyleIsolation: true
          }, // 是否开启沙箱
          singular: false, // 是否为单实例场景
        }
      }
    }
  },
  emits: ['app-mounted', 'app-unmounted'],
  setup(props, context) {
    const {appName, entry, componentName, stateData} = toRefs(props)
    const app = getCurrentInstance().appContext
    const {$eventBus, $actions} = app.config.globalProperties

    /**
      微应用实例引用 MicroApp = {
        mount(): Promise<null>;
        unmount(): Promise<null>;
        update(customProps: object): Promise<any>;
        getStatus(): | "NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" | "SKIP_BECAUSE_BROKEN" | "LOAD_ERROR";
        loadPromise: Promise<null>;
        bootstrapPromise: Promise<null>;
        mountPromise: Promise<null>;
        unmountPromise: Promise<null>;
      }
      */
    const subApp = ref(null)
    const state = reactive({
      loading: true,
      container: 'MicroApp_' + idIndex++
    })

    if (!cacheLoadStatus.has(props.appName)) {
      cacheLoadStatus.set(props.appName, 'NOT_LOADED')
    }

    const getAppConfig = () => {
      return {
        name: props.appName,
        entry: props.entry,
        container: `#${state.container}`,
        props: {
          appId: state.container,
          mode: 'load',
          componentName: props.componentName,
          stateData: props.stateData,
          appGlobal: {
            ...app.config.globalProperties,
            $extends: props.extends
          }
        },
      }
    }

    const mountedMicroApp = () => {
      const appConfig = getAppConfig()
      state.loading = true
      if (cacheLoadStatus.get(appConfig.name) == 'NOT_LOADED') {
        cacheLoadStatus.set(appConfig.name, 'LOADING')
      }
      subApp.value = loadMicroApp(appConfig, props.configuration)
      // subApp.value.loadPromise.then(() => {
      //   console.log('loadPromise', subApp.value.getStatus())
      // })
      subApp.value.bootstrapPromise.then(() => {
        // console.log('bootstrapPromise', subApp.value.getStatus())
        if(cacheLoadStatus.get(appConfig.name) == 'LOADING') {
          cacheLoadStatus.set(appConfig.name, subApp.value.getStatus())
          $eventBus.emit('sub-app-loaded', {appName: appConfig.name})
        }
      })
      subApp.value.mountPromise.then(() => {
        state.loading = false
        context.emit('app-mounted', {
          appId: state.container
        })
      })
    }

    const unmountMicroApp = () => {
      if(subApp.value && subApp.value.getStatus() == 'MOUNTED') {
        subApp.value.unmount(getAppConfig()).then(() => {
          subApp.value = null
          context.emit('app-unmounted')
        })
      }
    }

    const updateMicroApp = () => {
      if(subApp.value && subApp.value.getStatus() == 'MOUNTED') {
        subApp.value && subApp.value.update(getAppConfig())
      }
    }

    const subAppLoaded = ({appName}) => {
      if (appName == props.appName && !subApp.value) {
        mountedMicroApp()
      }
    }

    // 组件名称和状态
    watch(stateData, (stateData) => {
      console.log('LoadMicroApp数据改变', stateData)
      updateMicroApp()
      $eventBus.emit('load-micro-update', getAppConfig())
    }, {deep: true})

    watch([appName, entry, componentName], ([appName, entry, componentName], [prevAppName, prevEntry, prevComponentName]) => {
      // console.log('子应用配置改变')
      if (appName != prevAppName || entry != prevEntry) {
        // 卸载后重新加载
        unmountMicroApp()
        mountedMicroApp()
      } else {
        updateMicroApp()
      }
    })

    onMounted(() => {
      if (cacheLoadStatus.get(props.appName) != 'LOADING') {
        mountedMicroApp()
      }
      $eventBus.on('sub-app-loaded', subAppLoaded)
    })

    onBeforeUnmount(() => {
      $eventBus.off('sub-app-loaded', subAppLoaded)
      unmountMicroApp()
    })

    onActivated(() => {
      if (!state.loading && cacheLoadStatus.get(props.appName) != 'LOADING') {
        mountedMicroApp()
      }
      $eventBus.on('sub-app-loaded', subAppLoaded)
    })

    onDeactivated(() => {
      $eventBus.off('sub-app-loaded', subAppLoaded)
      unmountMicroApp()
    })

    return {
      ...toRefs(state)
    }
  }
}
</script>
<style lang="scss" scoped>
.sub-app{
  height: 100%;
  min-height: 200px;
  background-color: #fff;
}
</style>
